iT邦幫忙

2023 iThome 鐵人賽

DAY 16
0
Modern Web

前端開發之那些我會的與我不會的技術系列 第 16

React中的useReducer Hook:簡單解釋和範例

  • 分享至 

  • xImage
  •  

今天要介紹React的另一個Hook-useReducer,useReducer和useState一樣都是用於狀態管理的Hook,在同個專案或是component裡useState和useReducer可以搭配交互使用沒有影響。在使用的選擇上,如果狀態的資料結構比較複雜且應用本身會對於狀態做不同的操作,像是新增、修改和刪除等,不是單一的狀態存取,就可以考慮使用useReducer,幫助我們寫出更容易理解與debug的程式。

流程

主要可以分為3個區塊

state:狀態

dispatch:派送行為

reducer:執行操作
https://ithelp.ithome.com.tw/upload/images/20231001/20162751BtVNxpmkDx.jpg

  1. 使用者操作觸發帶有action(新狀態和哪種操作行為)的dispatch。
  2. 將新狀態與操作種類送到事先定義好的reducer進行運算並回傳更新的狀態。
  3. 狀態變更並且更新畫面。

使用

假設我們要做一個有新增和刪除的todo list

定義reducer

reducer會帶有原本state和傳入的action兩個參數,然後action會帶有type(動作種類)和payload(更新狀態需要的內容)

function tasksReducer(state, action) {
  switch(action.type) {
    case 'add': {
      return [...state, action.payload]
    }
    case 'delete': {
      return state.filter((task) => task.id !== action.payload.id)
    }
  }
}

宣告useReducer

帶入定義好的reducer和初始狀態值,會回傳一個帶有狀態和dispatch的陣列

const [tasks, taskDispatch] = useReducer(tasksReducer, initialTasks)

使用dispatch

// 新增
taskDispatch({type: 'add', payload: {id: createId(), text: todo, done: false}})

// 有時候我也會將action包成function使用,會更簡潔一點
function addAction(todo) {
  return {type: 'add', payload: {id: createId(), text: todo, done: false}}
}
taskDispatch(addAction(todo));

//刪除
taskDispatch({type: 'delete', payload: task})
// 有時候我也會將action包成function使用,會更簡潔一點
function delAction(todo) {
  return {type: 'delete', payload: todo}
}
taskDispatch(delAction(task))

完整範例

import { useReducer, useState } from 'react'

function App() {
  const [tasks, taskDispatch] = useReducer(tasksReducer, initialTasks)
  const [todo, setTodo] = useState('');
console.log(tasks);
  return (
    <>
      <ul>
        {
          tasks.map((task) => {
            return <li key={task.id}>
              {task.text} 
              <button onClick={() => taskDispatch(delAction(task))}>刪除</button>
            </li>
          })
        }
        <input type="text" value={todo} onChange={(e) => setTodo(e.target.value)}/>
        <button onClick={() => {
          taskDispatch(addAction(todo));
        }}>新增</button>
      </ul>
    </>
  )
}

function addAction(todo) {
  return {type: 'add', payload: {id: createId(), text: todo, done: false}}
}
function delAction(todo) {
  return {type: 'delete', payload: todo}
}
function createId() {
  return new Date().getTime();
}
function tasksReducer(state, action) {
  switch(action.type) {
    case 'add': {
      return [...state, action.payload]
    }
    case 'delete': {
      return state.filter((task) => task.id !== action.payload.id)
    }
		default: {
      return state
    }
  }
}

const initialTasks = [
  {id: 0, text: '吃飯', done: true},
  {id: 1, text: '睡覺', done: false},
  {id: 2, text: '打電動', done: false},
];

export default App

reducer很像reduce

useReducer這個名稱取的很像是我們陣列裡用的reduce函式,但是不只名字很像,實際上在做的事情也差不多,所以React才會以reducer來命名。

const initialTasks = [
  {id: 0, text: '吃飯', done: true},
  {id: 1, text: '睡覺', done: false},
  {id: 2, text: '打電動', done: false},
];

function tasksReducer(state, action) {
  switch(action.type) {
    case 'add': {
      return [...state, action.payload]
    }
    case 'delete': {
      return state.filter((task) => task.id !== action.payload.id)
    }
  }
}
// 類似我們js reduce的用法,像是reduce compose的用法
initialTasks.reduce(tasksReducer, initialTasks)

個人使用經驗

官方文件有提到可以使用**Immer**直接對於狀態進行操作,但是本身其實也蠻習慣會使用Spread Operator[…state],要直接修改物件或是陣列我一整個覺得不習慣,有沒有人也是這樣的,歡迎分享。

參考

https://react.dev/learn/extracting-state-logic-into-a-reducer


上一篇
了解React生命周期
下一篇
React中使用useContext傳遞資料
系列文
前端開發之那些我會的與我不會的技術31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言